{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cf7ccb32",
   "metadata": {},
   "source": [
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/langchain-ai/langchain-academy/blob/main/module-2/chatbot-external-memory.ipynb) [![Open in LangChain Academy](https://cdn.prod.website-files.com/65b8cd72835ceeacd4449a53/66e9eba12c7b7688aa3dbb5e_LCA-badge-green.svg)](https://academy.langchain.com/courses/take/intro-to-langgraph/lessons/58239440-lesson-6-chatbot-w-summarizing-messages-and-external-memory)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "af6c7afe-1037-41ab-98e4-494692e47402",
   "metadata": {},
   "source": [
    "# Chatbot with message summarization & external DB memory\n",
    "\n",
    "## Review\n",
    "\n",
    "We've covered how to customize graph state schema and reducer. \n",
    " \n",
    "We've also shown a number of tricks for trimming or filtering messages in graph state. \n",
    "\n",
    "We've used these concepts in a Chatbot with memory that produces a running summary of the conversation.\n",
    "\n",
    "## Goals\n",
    "\n",
    "But, what if we want our Chatbot to have memory that persists indefinitely?\n",
    "\n",
    "Now, we'll introduce some more advanced checkpointers that support external databases. \n",
    "\n",
    "Here, we'll show how to use [Sqlite as a checkpointer](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer), but other checkpointers, such as [Postgres](https://langchain-ai.github.io/langgraph/how-tos/persistence_postgres/) are available!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "85ed78d9-6ca2-45ac-96a9-52e341ec519d",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install --quiet -U langgraph-checkpoint-sqlite langchain_core langgraph langchain_openai"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "2e10c4d4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "OPENAI_API_KEY:  ········\n"
     ]
    }
   ],
   "source": [
    "import os, getpass\n",
    "\n",
    "def _set_env(var: str):\n",
    "    if not os.environ.get(var):\n",
    "        os.environ[var] = getpass.getpass(f\"{var}: \")\n",
    "\n",
    "_set_env(\"OPENAI_API_KEY\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b40d25c0-e9b5-4854-bf07-3cc3ff07122e",
   "metadata": {},
   "source": [
    "## Sqlite\n",
    "\n",
    "A good starting point here is the [SqliteSaver checkpointer](https://langchain-ai.github.io/langgraph/concepts/low_level/#checkpointer).\n",
    "\n",
    "Sqlite is a [small, fast, highly popular](https://x.com/karpathy/status/1819490455664685297) SQL database. \n",
    " \n",
    "If we supply `\":memory:\"` it creates an in-memory Sqlite database."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "fae15402-17ae-4e89-8ecf-4c89e08b22fe",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sqlite3\n",
    "# In memory\n",
    "conn = sqlite3.connect(\":memory:\", check_same_thread = False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c2bf53ec-6d4a-42ce-8183-344795eed403",
   "metadata": {},
   "source": [
    "But, if we supply a db path, then it will create a database for us!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "58339167-920c-4994-a0a7-0a9c5d4f7cf7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# pull file if it doesn't exist and connect to local db\n",
    "!mkdir -p state_db && [ ! -f state_db/example.db ] && wget -P state_db https://github.com/langchain-ai/langchain-academy/raw/main/module-2/state_db/example.db\n",
    "\n",
    "db_path = \"state_db/example.db\"\n",
    "conn = sqlite3.connect(db_path, check_same_thread=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "3c7736b6-a750-48f8-a838-8e7616b12250",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Here is our checkpointer \n",
    "from langgraph.checkpoint.sqlite import SqliteSaver\n",
    "memory = SqliteSaver(conn)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d8cb629-213f-4b87-965e-19b812c42da1",
   "metadata": {},
   "source": [
    "Let's re-define our chatbot."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "dc414e29-2078-41a0-887c-af1a6a3d72c0",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_core.messages import SystemMessage, HumanMessage, RemoveMessage\n",
    "\n",
    "from langgraph.graph import END\n",
    "from langgraph.graph import MessagesState\n",
    "\n",
    "model = ChatOpenAI(model=\"gpt-4o-mini\",temperature=0)\n",
    "\n",
    "class State(MessagesState):\n",
    "    summary: str\n",
    "\n",
    "# Define the logic to call the model\n",
    "def call_model(state: State):\n",
    "    \n",
    "    # Get summary if it exists\n",
    "    summary = state.get(\"summary\", \"\")\n",
    "\n",
    "    # If there is summary, then we add it\n",
    "    if summary:\n",
    "        \n",
    "        # Add summary to system message\n",
    "        system_message = f\"Summary of conversation earlier: {summary}\"\n",
    "\n",
    "        # Append summary to any newer messages\n",
    "        messages = [SystemMessage(content=system_message)] + state[\"messages\"]\n",
    "    \n",
    "    else:\n",
    "        messages = state[\"messages\"]\n",
    "    \n",
    "    response = model.invoke(messages)\n",
    "    return {\"messages\": response}\n",
    "\n",
    "def summarize_conversation(state: State):\n",
    "    \n",
    "    # First, we get any existing summary\n",
    "    summary = state.get(\"summary\", \"\")\n",
    "\n",
    "    # Create our summarization prompt \n",
    "    if summary:\n",
    "        \n",
    "        # A summary already exists\n",
    "        summary_message = (\n",
    "            f\"This is summary of the conversation to date: {summary}\\n\\n\"\n",
    "            \"Extend the summary by taking into account the new messages above:\"\n",
    "        )\n",
    "        \n",
    "    else:\n",
    "        summary_message = \"Create a summary of the conversation above:\"\n",
    "\n",
    "    # Add prompt to our history\n",
    "    messages = state[\"messages\"] + [HumanMessage(content=summary_message)]\n",
    "    response = model.invoke(messages)\n",
    "    \n",
    "    # Delete all but the 2 most recent messages\n",
    "    delete_messages = [RemoveMessage(id=m.id) for m in state[\"messages\"][:-2]]\n",
    "    return {\"summary\": response.content, \"messages\": delete_messages}\n",
    "\n",
    "# Determine whether to end or summarize the conversation\n",
    "def should_continue(state: State):\n",
    "    \n",
    "    \"\"\"Return the next node to execute.\"\"\"\n",
    "    \n",
    "    messages = state[\"messages\"]\n",
    "    \n",
    "    # If there are more than six messages, then we summarize the conversation\n",
    "    if len(messages) > 6:\n",
    "        return \"summarize_conversation\"\n",
    "    \n",
    "    # Otherwise we can just end\n",
    "    return END"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41c13c0b-a383-4f73-9cc1-63f0eed8f190",
   "metadata": {},
   "source": [
    "Now, we just re-compile with our sqlite checkpointer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "e867fd95-91eb-4ce1-82fc-bb72d611a96d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from IPython.display import Image, display\n",
    "from langgraph.graph import StateGraph, START\n",
    "\n",
    "# Define a new graph\n",
    "workflow = StateGraph(State)\n",
    "workflow.add_node(\"conversation\", call_model)\n",
    "workflow.add_node(summarize_conversation)\n",
    "\n",
    "# Set the entrypoint as conversation\n",
    "workflow.add_edge(START, \"conversation\")\n",
    "workflow.add_conditional_edges(\"conversation\", should_continue)\n",
    "workflow.add_edge(\"summarize_conversation\", END)\n",
    "\n",
    "# Compile\n",
    "graph = workflow.compile(checkpointer=memory)\n",
    "display(Image(graph.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8769db99-3938-45e6-a594-56beb18d6c45",
   "metadata": {},
   "source": [
    "Now, we can invoke the graph several times. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "0f4094a0-d240-4be8-903a-7d9f605bdc5c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Hello again, Lance! It's great to hear from you. Is there something specific you'd like to chat about today? Maybe more about the 49ers or something else on your mind?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "Your name is Lance! How can I assist you today?\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "That's awesome, Lance! The 49ers have a rich history and a passionate fan base. Is there a particular aspect of the team you enjoy discussing, like their current roster, memorable games, or maybe their prospects for the upcoming season?\n"
     ]
    }
   ],
   "source": [
    "# Create a thread\n",
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "\n",
    "# Start conversation\n",
    "input_message = HumanMessage(content=\"hi! I'm Lance\")\n",
    "output = graph.invoke({\"messages\": [input_message]}, config) \n",
    "for m in output['messages'][-1:]:\n",
    "    m.pretty_print()\n",
    "\n",
    "input_message = HumanMessage(content=\"what's my name?\")\n",
    "output = graph.invoke({\"messages\": [input_message]}, config) \n",
    "for m in output['messages'][-1:]:\n",
    "    m.pretty_print()\n",
    "\n",
    "input_message = HumanMessage(content=\"i like the 49ers!\")\n",
    "output = graph.invoke({\"messages\": [input_message]}, config) \n",
    "for m in output['messages'][-1:]:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0f3e842-4497-45e2-a924-69672a9bcb33",
   "metadata": {},
   "source": [
    "Let's confirm that our state is saved locally."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "d2ab158a-5a82-417a-8841-730a4cc18ea7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StateSnapshot(values={'messages': [HumanMessage(content=\"hi! I'm Lance\", additional_kwargs={}, response_metadata={}, id='fbadf538-3963-4b69-b3d4-e0dc49db5c47'), AIMessage(content=\"Hello again, Lance! It's great to hear from you. Is there something specific you'd like to chat about today? Maybe more about the 49ers or something else on your mind?\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 38, 'prompt_tokens': 337, 'total_tokens': 375, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_06737a9306', 'finish_reason': 'stop', 'logprobs': None}, id='run-44ea8379-19bd-42b3-9c47-a8fd858daa63-0', usage_metadata={'input_tokens': 337, 'output_tokens': 38, 'total_tokens': 375, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content=\"what's my name?\", additional_kwargs={}, response_metadata={}, id='43a85d00-8aa1-4eb2-b7c5-baaf7ac4fb20'), AIMessage(content='Your name is Lance! How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 221, 'total_tokens': 234, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_06737a9306', 'finish_reason': 'stop', 'logprobs': None}, id='run-68f4ba49-dfa3-458f-b085-03e8cc92f6cb-0', usage_metadata={'input_tokens': 221, 'output_tokens': 13, 'total_tokens': 234, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='i like the 49ers!', additional_kwargs={}, response_metadata={}, id='d15d85ad-fcc9-41ad-b9ea-e68da56a48cf'), AIMessage(content=\"That's awesome, Lance! The 49ers have a rich history and a passionate fan base. Is there a particular aspect of the team you enjoy discussing, like their current roster, memorable games, or maybe their prospects for the upcoming season?\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 49, 'prompt_tokens': 248, 'total_tokens': 297, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_06737a9306', 'finish_reason': 'stop', 'logprobs': None}, id='run-1d854593-ca34-4115-9a36-61b041e67791-0', usage_metadata={'input_tokens': 248, 'output_tokens': 49, 'total_tokens': 297, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})], 'summary': \"Here's an extended summary of the conversation to date:\\n\\nLance introduced himself twice during the conversation and expressed his fondness for the San Francisco 49ers football team. The AI assistant acknowledged Lance's name each time and showed willingness to discuss the 49ers, offering to talk about various aspects of the team, such as their history, current roster, memorable games, prospects for the upcoming season, rivalries, and their home stadium, Levi's Stadium. The conversation was brief and somewhat repetitive, with Lance reintroducing himself at the end without directly responding to the AI's questions or prompts about the 49ers. The AI continued to engage with Lance, inviting him to share more about his interests or thoughts related to the team or any other topic.\"}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1eff93ca-f6cd-6353-801b-46bdd4738cc7'}}, metadata={'source': 'loop', 'writes': {'conversation': {'messages': AIMessage(content=\"That's awesome, Lance! The 49ers have a rich history and a passionate fan base. Is there a particular aspect of the team you enjoy discussing, like their current roster, memorable games, or maybe their prospects for the upcoming season?\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 49, 'prompt_tokens': 248, 'total_tokens': 297, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_06737a9306', 'finish_reason': 'stop', 'logprobs': None}, id='run-1d854593-ca34-4115-9a36-61b041e67791-0', usage_metadata={'input_tokens': 248, 'output_tokens': 49, 'total_tokens': 297, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})}}, 'thread_id': '1', 'step': 27, 'parents': {}}, created_at='2025-03-04T21:07:30.827263+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1eff93ca-e861-6e84-801a-4e2c24eb6c17'}}, tasks=())"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "graph_state = graph.get_state(config)\n",
    "graph_state"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e21152d-ed9c-408d-b7d5-f634c9ce81e2",
   "metadata": {},
   "source": [
    "### Persisting state\n",
    "\n",
    "Using database like Sqlite means state is persisted! \n",
    "\n",
    "For example, we can re-start the notebook kernel and see that we can still load from Sqlite DB on disk.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "b9a44dc5-be04-45fa-a6fc-27b0f8ee4678",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StateSnapshot(values={'messages': [HumanMessage(content=\"hi! I'm Lance\", additional_kwargs={}, response_metadata={}, id='fbadf538-3963-4b69-b3d4-e0dc49db5c47'), AIMessage(content=\"Hello again, Lance! It's great to hear from you. Is there something specific you'd like to chat about today? Maybe more about the 49ers or something else on your mind?\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 38, 'prompt_tokens': 337, 'total_tokens': 375, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_06737a9306', 'finish_reason': 'stop', 'logprobs': None}, id='run-44ea8379-19bd-42b3-9c47-a8fd858daa63-0', usage_metadata={'input_tokens': 337, 'output_tokens': 38, 'total_tokens': 375, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content=\"what's my name?\", additional_kwargs={}, response_metadata={}, id='43a85d00-8aa1-4eb2-b7c5-baaf7ac4fb20'), AIMessage(content='Your name is Lance! How can I assist you today?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 221, 'total_tokens': 234, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_06737a9306', 'finish_reason': 'stop', 'logprobs': None}, id='run-68f4ba49-dfa3-458f-b085-03e8cc92f6cb-0', usage_metadata={'input_tokens': 221, 'output_tokens': 13, 'total_tokens': 234, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), HumanMessage(content='i like the 49ers!', additional_kwargs={}, response_metadata={}, id='d15d85ad-fcc9-41ad-b9ea-e68da56a48cf'), AIMessage(content=\"That's awesome, Lance! The 49ers have a rich history and a passionate fan base. Is there a particular aspect of the team you enjoy discussing, like their current roster, memorable games, or maybe their prospects for the upcoming season?\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 49, 'prompt_tokens': 248, 'total_tokens': 297, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_06737a9306', 'finish_reason': 'stop', 'logprobs': None}, id='run-1d854593-ca34-4115-9a36-61b041e67791-0', usage_metadata={'input_tokens': 248, 'output_tokens': 49, 'total_tokens': 297, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})], 'summary': \"Here's an extended summary of the conversation to date:\\n\\nLance introduced himself twice during the conversation and expressed his fondness for the San Francisco 49ers football team. The AI assistant acknowledged Lance's name each time and showed willingness to discuss the 49ers, offering to talk about various aspects of the team, such as their history, current roster, memorable games, prospects for the upcoming season, rivalries, and their home stadium, Levi's Stadium. The conversation was brief and somewhat repetitive, with Lance reintroducing himself at the end without directly responding to the AI's questions or prompts about the 49ers. The AI continued to engage with Lance, inviting him to share more about his interests or thoughts related to the team or any other topic.\"}, next=(), config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1eff93ca-f6cd-6353-801b-46bdd4738cc7'}}, metadata={'source': 'loop', 'writes': {'conversation': {'messages': AIMessage(content=\"That's awesome, Lance! The 49ers have a rich history and a passionate fan base. Is there a particular aspect of the team you enjoy discussing, like their current roster, memorable games, or maybe their prospects for the upcoming season?\", additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 49, 'prompt_tokens': 248, 'total_tokens': 297, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_06737a9306', 'finish_reason': 'stop', 'logprobs': None}, id='run-1d854593-ca34-4115-9a36-61b041e67791-0', usage_metadata={'input_tokens': 248, 'output_tokens': 49, 'total_tokens': 297, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})}}, 'thread_id': '1', 'step': 27, 'parents': {}}, created_at='2025-03-04T21:07:30.827263+00:00', parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1eff93ca-e861-6e84-801a-4e2c24eb6c17'}}, tasks=())"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Create a thread\n",
    "config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "graph_state = graph.get_state(config)\n",
    "graph_state"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8466e418-1a46-4cdb-a51a-6ae14281bb85",
   "metadata": {},
   "source": [
    "## LangGraph Studio\n",
    "\n",
    "--\n",
    "\n",
    "**⚠️ DISCLAIMER**\n",
    "\n",
    "*Running Studio currently requires a Mac. If you are not using a Mac, then skip this step.*\n",
    "\n",
    "--\n",
    "\n",
    "Now that we better understand external memory, recall that the LangGraph API packages your code and provides you with with built-in persistence.\n",
    " \n",
    "And the API is the back-end for Studio! \n",
    "\n",
    "Load the `chatbot` in the UI, which uses `module2-/studio/chatbot.py` set in `module2-/studio/langgraph.json`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "57f236ba-b437-41c1-bce3-28d740545e2d",
   "metadata": {},
   "outputs": [],
   "source": [
    "#learned how to use external memory with sqlite\n",
    "#so interrupting the kernal doesnt affect memory!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
